package MicroSpring.beanFactory.impl;

import MicroSpring.beanFactory.BeanFactory;
import MicroSpring.beanFactoryPostProcessor.BeanFactoryPostProcessor;
import MicroSpring.beanPostProcessor.BeanPostProcessor;
import MicroSpring.registry.BeanDefinitionRegistry;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

/*
    BeanFactory的主要实现类：DefaultListableBeanFactory
 */
public class DefaultListableBeanFactory implements BeanFactory, BeanDefinitionRegistry {

    public final static String SINGLETON="singleton";
    private final String LOADER_CLASS_PATH = Thread.currentThread().getContextClassLoader().getResource("").getPath();
    private String FINAL_SCAN_PATH;
    private final ConcurrentHashMap<String,BeanDefinition> beanDefinitionMap;
    private final ConcurrentHashMap<String,Object>singletonBeanObjects;
    private final List<BeanPostProcessor> beanPostProcessorList;
    private final List<BeanFactoryPostProcessor> beanFactoryPostProcessorList;
    private final Set<String> beanDefinitionNames;
    private final Map<String,List<Method>> dealingInjectMethods;
    private final Map<Class<?>, String[]> allBeanNamesByType;
    private final Map<Class,Object>beanOfType;
    private final Map<Class,String>singletonBeanNameOfType;
    private final Map<String,List<Field>> lazyInjectBeans;
    private boolean isOpenAspect;
    private final ClassLoader classLoader;

    private final Map<String,Object>aspectBeans;
    private final Map<Class,Annotation[]>typeAnnotations;
    private final Map<Class,Map<Annotation,Method>>typeMethodAnnotations;

    private final Map<String,List<Field>>beansInjectBeans;
    private boolean isGivenScap;
    private Class bootClass;

    public DefaultListableBeanFactory() {
        beanDefinitionMap=new ConcurrentHashMap<String, BeanDefinition>(256);
        singletonBeanObjects=new ConcurrentHashMap<>(256);
        beanPostProcessorList=new ArrayList<>(256);
        beanFactoryPostProcessorList=new ArrayList<>(256);
        beanDefinitionNames=new HashSet<>(256);
        allBeanNamesByType=new ConcurrentHashMap<>(256);
        dealingInjectMethods=new ConcurrentHashMap<>(128);
        beanOfType=new ConcurrentHashMap<>(256);
        singletonBeanNameOfType=new ConcurrentHashMap<>();
        lazyInjectBeans=new ConcurrentHashMap<>(256);
        classLoader=Thread.currentThread().getContextClassLoader();
        aspectBeans=new ConcurrentHashMap<>(256);
        typeAnnotations=new ConcurrentHashMap<>();
        typeMethodAnnotations=new ConcurrentHashMap<>();
        beansInjectBeans=new ConcurrentHashMap<>();
    }

    public void addLazyInjectBeans(String beanName, Field field) {
        List<Field> l = this.lazyInjectBeans.getOrDefault(beanName, new ArrayList<>());
        l.add(field);
        this.lazyInjectBeans.put(beanName,l);
    }
    public Map<String,List<Field>> getLazyInjectBeans(){
        return this.lazyInjectBeans;
    }


    public void setFINAL_SCAN_PATH(String finalScanPath) {
        this.FINAL_SCAN_PATH = finalScanPath;
        setGivenScap(true);
    }

    public boolean isGivenScap() {
        return isGivenScap;
    }
    public boolean isBootStrap(){
        return this.bootClass!=null;
    }
    public void setGivenScap(boolean givenScap) {
        isGivenScap = givenScap;
    }


    public String getFINAL_SCAN_PATH() {
        return (FINAL_SCAN_PATH!=null&&FINAL_SCAN_PATH.length()>0)?FINAL_SCAN_PATH:LOADER_CLASS_PATH;
    }

    public Class getBootClass() {
        return bootClass;
    }

    public void setBootClass(Class bootClass) {
        this.bootClass = bootClass;
    }

    @Override
    public void removeBeanDefinition(String var1) {
        this.getBeanDefinitionMap().remove(var1);
    }

    public List<BeanPostProcessor> getBeanPostProcessorList() {
        return beanPostProcessorList;
    }

    public List<BeanFactoryPostProcessor> getBeanFactoryPostProcessorList() {
        return beanFactoryPostProcessorList;
    }

    public Map<String, List<Method>> getDealingInjectMethods() {
        return dealingInjectMethods;
    }



    @Override
    public BeanDefinition getBeanDefinition(String var1) {
        return this.getBeanDefinitionMap().get(var1);
    }

    @Override
    public boolean containsBeanDefinition(String var1) {
        return this.beanDefinitionMap.containsKey(var1);
    }

    /**
     * *
     * @param beanName
     * @param beanDefinition
     * *
     */
    public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition){
        this.beanDefinitionMap.put(beanName,beanDefinition);
        this.beanDefinitionNames.add(beanName);
    }

    /**
     * * 注册单例bean
     * @param beanName
     * @param singletonObject
     */
    public void registerSingleton(String beanName, Object singletonObject){
        Class<?> yClazz = singletonObject.getClass();
        if (!aspectBeans.containsKey(beanName)) {
            this.singletonBeanNameOfType.put(singletonObject.getClass(), beanName);
        }

        if (!singletonBeanNameOfType.containsKey(yClazz)){
            Class<?> aClass = this.singletonBeanObjects.get(beanName).getClass();
            if (singletonBeanNameOfType.containsKey(aClass))
                this.beanOfType.put(aClass,singletonObject);
        }else {
            this.beanOfType.put(yClazz, singletonObject);
        }
        this.singletonBeanObjects.put(beanName,singletonObject);
    }


    /**
     * 添加beanFactory后处理器
     * @param processor
     */
    public void addBeanFactoryPostProcessor(BeanFactoryPostProcessor processor){
        beanFactoryPostProcessorList.add(processor);
    }

    /**
     * * 添加bean后处理器
     * @param processor
     */
    public void addBeanPostProcessor(BeanPostProcessor processor){
        beanPostProcessorList.add(processor);
    }



    public void addSingletonsObjects(String beanName,Object object){
        if (beanDefinitionMap.containsKey(beanName))
            singletonBeanObjects.put(beanName,object);
    }

    /**
     **添加需要注入的bean。
     * @param beanName
     * @param method
     */
    public void addDealingInjectMethods(String beanName,Method method){
        List<Method> methodList = this.dealingInjectMethods.getOrDefault(beanName, new ArrayList<>());
        for (int i = 0; i < methodList.size(); i++) {
            if (methodList.get(i).getClass() == method.getClass()){
                methodList.set(i,method);
                this.dealingInjectMethods.put(beanName,methodList);
                return;
            }
        }
        methodList.add(method);
        this.dealingInjectMethods.put(beanName,methodList);
    }

    public Collection<String> getBeanDefinitionNames() {
        return this.beanDefinitionNames;
    }




    @Override
    public int getBeanDefinitionCount() {
        return beanDefinitionMap.size();
    }

    @Override
    public boolean isBeanNameInUse(String var1) {
        return beanDefinitionNames.contains(var1);
    }

    public ConcurrentHashMap<String, BeanDefinition> getBeanDefinitionMap() {
        return beanDefinitionMap;
    }

    public ConcurrentHashMap<String, Object> getSingletonBeanObjects() {
        return singletonBeanObjects;
    }

    @Override
    public Object getBean(String beanName) {
        return this.getSingletonBeanObjects().getOrDefault(beanName,null);
    }

    @Override
    public <T> T getBean(Class<T> clazz) {
        return (T)this.beanOfType.get(clazz);
    }

    @Override
    public boolean containsBean(String beanName) {
        return singletonBeanObjects.containsKey(beanName);
    }

    public boolean containsBean(Class clazz) {
        return singletonBeanNameOfType.containsKey(clazz);
    }

    @Override
    public boolean isSingleton(String beanName) {
        return false;
    }

    @Override
    public boolean isPrototype(String beanName) {
        return false;
    }

    @Override
    public Class<?> getType(String beanName) {
        return null;
    }

    public void replaceRegistedBean(Object replaceBean) {
        String beanName=getSingletonBeanNameOfType(replaceBean.getClass());
        singletonBeanObjects.put(beanName,replaceBean);
    }

    public String getSingletonBeanNameOfType(Class<?> aClass) {
        return this.singletonBeanNameOfType.get(aClass);
    }

    public void removeDealingMethod(String methodInvokeBeanName) {
        dealingInjectMethods.remove(methodInvokeBeanName);
    }

    public synchronized void destroyBeans() {
        this.singletonBeanObjects.clear();
        this.beanDefinitionMap.clear();
        this.beanDefinitionNames.clear();
        this.beanPostProcessorList.clear();
        this.beanFactoryPostProcessorList.clear();
        this.beanOfType.clear();
        this.allBeanNamesByType.clear();
        this.singletonBeanNameOfType.clear();
        this.dealingInjectMethods.clear();
    }

    public boolean isOpenAspect() {
        return this.isOpenAspect;
    }

    public void setOpenAspect(boolean b) {
        this.isOpenAspect=b;
    }

    public ClassLoader getClassLoader() {
        return this.classLoader;
    }

    public void addAspectBean(String beanName, Object aspectInstance) {
        this.aspectBeans.put(beanName,aspectInstance);
    }
    public void clearAspectBeans(){
        this.aspectBeans.clear();
    }

    public Map<String, Object> getAspectBeans() {
        return aspectBeans;
    }


    public void addTypeAnnotations(Class clazz, Annotation[] annotations) {
        this.typeAnnotations.put(clazz,annotations);
    }


    public void addTypeMethodAnnotations(Class clazz, Map<Annotation, Method> methodAnnotations) {
        this.typeMethodAnnotations.put(clazz,methodAnnotations);
    }

    public Annotation[] getTypeAnnotationsByType(Class clazz){
        return typeAnnotations.get(clazz);
    }

    public Map<Annotation, Method> getTypeMethodAnnotationsByType(Class clazz){
        Map<Annotation, Method> annotationMethodMap = typeMethodAnnotations.get(clazz);
        return annotationMethodMap;
    }

    public Map<Class, Annotation[]> getTypeAnnotations() {
        return typeAnnotations;
    }

    public Map<Class, Map<Annotation, Method>> getTypeMethodAnnotations() {
        return typeMethodAnnotations;
    }


    public Map<String, List<Field>> getBeansInjectBeans() {
        return beansInjectBeans;
    }

    public void addBeansInjectBeans(String targetBeanName,Field injectedBeanField){
        List<Field> list = beansInjectBeans.getOrDefault(targetBeanName,new ArrayList<>());
        list.add(injectedBeanField);
        beansInjectBeans.put(targetBeanName,list);
    }
}
